package com.cll.prototype.common.util;

import com.cll.prototype.common.dictionary.AlgorithmType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * 描述信息:
 * 加解密工具类
 * @author CLL
 * @version 1.0
 * @date 2020/10/26 20:05
 */
public class EncryptUtils {

    private static final Logger logger = LoggerFactory.getLogger(EncryptUtils.class);

    private static final String HEX_CHAR = "0123456789ABCDEF";

    public static final String DEFAULT_AES_KEY = "0123456789";

    public static final String DEFAULT_AES_VECTOR = "9876543210";

    /**
     * 用MD5算法进行加密
     *
     * @param content 需要加密的字符串
     * @return MD5加密后的结果
     */
    public static String encryptUseMD5(String content) {
        return encode(content, AlgorithmType.MD5.getMethod());
    }

    /**
     * 用SHA算法进行加密
     *
     * @param content 需要加密的字符串
     * @return SHA加密后的结果
     */
    public static String encryptUseSHA(String content) {
        return encode(content, AlgorithmType.SHA.getMethod());
    }

    /**
     * 加密
     * 1.构造密钥生成器
     * 2.根据encodeRules规则初始化密钥生成器
     * 3.产生密钥
     * 4.创建和初始化密码器
     * 5.内容加密
     * 6.返回字符串
     * @param encodeRules   加密规则（密钥）
     * @param content   待加密内容
     * @return  加密之后的内容
     */
    public static String encryptUseAES(String encodeRules,String content){
        try {
            //5.根据字节数组生成AES密钥
            SecretKey key=generateSecretKey(encodeRules);
            //6.根据指定算法AES自成密码器
            Cipher cipher=Cipher.getInstance(AlgorithmType.AES.getMethod());
            //7.初始化密码器，第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作，第二个参数为使用的KEY
            cipher.init(Cipher.ENCRYPT_MODE, key);
            //8.获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
            byte [] byteEncode = content.getBytes(StandardCharsets.UTF_8);
            //9.根据密码器的初始化方式--加密：将数据加密
            byte [] byteResult = cipher.doFinal(byteEncode);
            //10.将加密后的数据转换为字符串
            //这里用Base64Encoder中会找不到包
            //解决办法：
            //在项目的Build path中先移除JRE System Library，再添加库JRE System Library，重新编译后就一切正常了。
            return new BASE64Encoder().encode(byteResult);
        } catch (NoSuchAlgorithmException e) {
            logger.error("******NoSuchAlgorithmException" + e.getMessage(), e);
        } catch (NoSuchPaddingException e) {
            logger.error("******NoSuchPaddingException" + e.getMessage(), e);
        } catch (InvalidKeyException e) {
            logger.error("******InvalidKeyException" + e.getMessage(), e);
        } catch (IllegalBlockSizeException e) {
            logger.error("******IllegalBlockSizeException" + e.getMessage(), e);
        } catch (BadPaddingException e) {
            logger.error("******BadPaddingException" + e.getMessage(), e);
        }
        return null;
    }


    /**
     * 解密
     * 解密过程：
     * 1.同加密1-4步
     * 2.将加密后的字符串转换成byte[]数组
     * 3.将加密内容解密
     * @param encodeRules   加密规则密钥
     * @param content   加密之后的字符串
     * @return  解密后的字符串
     */
    public static String decryptUseAES(String encodeRules,String content){
        try {
            //5.根据字节数组生成AES密钥
            SecretKey key=generateSecretKey(encodeRules);
            //6.根据指定算法AES自成密码器
            Cipher cipher=Cipher.getInstance(AlgorithmType.AES.getMethod());
            //7.初始化密码器，第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作，第二个参数为使用的KEY
            cipher.init(Cipher.DECRYPT_MODE, key);
            //8.将加密并编码后的内容解码成字节数组
            byte [] byteContent= new BASE64Decoder().decodeBuffer(content);
            // 9. 解密
            byte [] byteDecode=cipher.doFinal(byteContent);
            return new String(byteDecode, StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException e) {
            logger.error("******NoSuchAlgorithmException" + e.getMessage(), e);
        } catch (NoSuchPaddingException e) {
            logger.error("******NoSuchPaddingException" + e.getMessage(), e);
        } catch (InvalidKeyException e) {
            logger.error("******InvalidKeyException" + e.getMessage(), e);
        } catch (IOException e) {
            logger.error("******IOException" + e.getMessage(), e);
        } catch (IllegalBlockSizeException e) {
            logger.error("******IllegalBlockSizeException" + e.getMessage(), e);
        } catch (BadPaddingException e) {
            logger.error("******BadPaddingException" + e.getMessage(), e);
        }
        return null;
    }

    /**
     * 用base64算法进行加密
     *
     * @param content 需要加密的字符串
     * @return base64加密后的结果
     */
    public static String encodeUseBase64(String content) {
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(content.getBytes());
    }

    /**
     * 用base64算法进行解密
     *
     * @param str 需要解密的字符串
     * @return base64解密后的结果
     * @throws IOException 读写异常
     */
    public static String decodeUseBase64(String str) throws IOException {
        BASE64Decoder encoder = new BASE64Decoder();
        return new String(encoder.decodeBuffer(str));
    }

    /**
     * 对指定字符串进行指定方式的加密
     * @param sourceStr   待加密字符串
     * @param method    加密方法
     * @return  加密之后的字符串.如果为空表示加密失败
     */
    private static String encode(String sourceStr, String method) {
        MessageDigest md;
        String targetStr = null;
        try {
            md = MessageDigest.getInstance(method);
            md.update(sourceStr.getBytes());
            targetStr = new BigInteger(1, md.digest()).toString(16);
        } catch (NoSuchAlgorithmException e) {
            logger.error("****** encrypt str throw exception... sourceStr = {}, encode method = {}" + e.getMessage(), sourceStr, method, e);
        }
        return targetStr;
    }

    /**
     * 生成算法的矢量标准
     * @param algorithmParameterSpec    相当于添加混淆盐值
     * @return  密钥
     */
    private static IvParameterSpec generateIvParameterSpec(String algorithmParameterSpec) {
        return new IvParameterSpec(algorithmParameterSpec.getBytes());
    }

    /**
     * 生成密钥信息
     * @param encodeRule    密钥规则
     * @return  密钥
     * @throws NoSuchAlgorithmException 不支持的算法
     */
    private static SecretKey generateSecretKey(String encodeRule) throws NoSuchAlgorithmException {
        //1.构造密钥生成器，指定为AES算法,不区分大小写
        KeyGenerator keyGenerator = KeyGenerator.getInstance(AlgorithmType.AES.getMethod());
        //2.根据encodeRules规则初始化密钥生成器,生成一个128位的随机源,根据传入的字节数组
        keyGenerator.init(128, new SecureRandom(encodeRule.getBytes()));
        //3.产生原始对称密钥
        SecretKey originalKey = keyGenerator.generateKey();
        //4.获得原始对称密钥的字节数组
        byte [] raw = originalKey.getEncoded();
        //5.根据字节数组生成AES密钥
        return new SecretKeySpec(raw, AlgorithmType.AES.getMethod());
    }

    /**
     * 十六进制字符串转换成byte数组
     * @param hexStr 十六进制字符串
     * @return 子节数组
     */
    private static byte[] hexStr2Bytes(String hexStr) {
        char[] hexArr = hexStr.toCharArray();
        byte[] bytes = new byte[hexStr.length() / 2];
        int n;
        for (int i = 0; i < bytes.length; i++) {
            n = HEX_CHAR.indexOf(hexArr[2 * i]) * 16;
            n += HEX_CHAR.indexOf(hexArr[2 * i + 1]);
            bytes[i] = (byte) (n & 0xff);
        }
        return bytes;
    }

    /**
     * byte数组转换成十六进制字符串
     * @param b 子节数组
     * @return 16进制字符串
     */
    private static String byte2HexStr(byte[] b) {
        StringBuilder hs = new StringBuilder();
        String strTemp;
        for (byte value : b) {
            strTemp = (Integer.toHexString(value & 0XFF));
            if (strTemp.length() == 1) {
                hs.append("0").append(strTemp);
            } else {
                hs.append(strTemp);
            }
        }
        return hs.toString().toUpperCase();
    }
}